package com.gitee.wsl.security.hash

import com.gitee.wsl.ext.array.readLong
import com.gitee.wsl.ext.array.readShort
import com.gitee.wsl.ext.base.Arrays.copyOfRange
import kotlin.jvm.JvmOverloads


/**
 * Apache 发布的MetroHash算法，是一组用于非加密用例的最先进的哈希函数。
 * 除了卓越的性能外，他们还以算法生成而著称。
 *
 *
 *
 * 官方实现：https://github.com/jandrewrogers/MetroHash
 * 官方文档：http://www.jandrewrogers.com/2015/05/27/metrohash/
 * Go语言实现：https://github.com/linvon/cuckoo-filter/blob/main/vendor/github.com/dgryski/go-metro/
 * @author li
 */
object MetroHash {
    /**
     * hash64 种子加盐
     */
    private const val k0_64: Long = -0x292fe70b
    private const val k1_64: Long = -0x5d55fcc5
    private const val k2_64: Long = 0x62992FC1
    private const val k3_64: Long = 0x30BC5B29

    /**
     * hash128 种子加盐
     */
    private const val k0_128: Long = -0x37c56e1f
    private const val k1_128: Long = -0x79b72425
    private const val k2_128: Long = 0x7BDEC03B
    private const val k3_128: Long = 0x2F5870A5

    @JvmOverloads
    fun hash64(data: ByteArray, seed: Long = 1337): Long {
        var buffer = data
        var hash = (seed + k2_64) * k0_64

        var v0: Long
        var v1: Long
        var v2: Long
        var v3: Long
        v0 = hash
        v1 = hash
        v2 = hash
        v3 = hash

        if (buffer.size >= 32) {
            while (buffer.size >= 32) {
                v0 += littleEndian64(buffer, 0) * k0_64
                v0 = rotateLeft64(v0, -29) + v2
                v1 += littleEndian64(buffer, 8) * k1_64
                v1 = rotateLeft64(v1, -29) + v3
                v2 += littleEndian64(buffer, 24) * k2_64
                v2 = rotateLeft64(v2, -29) + v0
                v3 += littleEndian64(buffer, 32) * k3_64
                v3 = rotateLeft64(v3, -29) + v1
                buffer = copyOfRange(buffer, 32, buffer.size)
            }

            v2 = v2 xor rotateLeft64(((v0 + v3) * k0_64) + v1, -37) * k1_64
            v3 = v3 xor rotateLeft64(((v1 + v2) * k1_64) + v0, -37) * k0_64
            v0 = v0 xor rotateLeft64(((v0 + v2) * k0_64) + v3, -37) * k1_64
            v1 = v1 xor rotateLeft64(((v1 + v3) * k1_64) + v2, -37) * k0_64
            hash += v0 xor v1
        }

        if (buffer.size >= 16) {
            v0 = hash + littleEndian64(buffer, 0) * k2_64
            v0 = rotateLeft64(v0, -29) * k3_64
            v1 = hash + littleEndian64(buffer, 8) * k2_64
            v1 = rotateLeft64(v1, -29) * k3_64
            v0 = v0 xor rotateLeft64(v0 * k0_64, -21) + v1
            v1 = v1 xor rotateLeft64(v1 * k3_64, -21) + v0
            hash += v1
            buffer = copyOfRange(buffer, 16, buffer.size)
        }

        if (buffer.size >= 8) {
            hash += littleEndian64(buffer, 0) * k3_64
            buffer = copyOfRange(buffer, 8, buffer.size)
            hash = hash xor rotateLeft64(hash, -55) * k1_64
        }

        if (buffer.size >= 4) {
            hash += littleEndian32(copyOfRange(buffer, 0, 4)).toLong() * k3_64
            hash = hash xor rotateLeft64(hash, -26) * k1_64
            buffer = copyOfRange(buffer, 4, buffer.size)
        }

        if (buffer.size >= 2) {
            hash += littleEndian16(copyOfRange(buffer, 0, 2)).toLong() * k3_64
            buffer = copyOfRange(buffer, 2, buffer.size)
            hash = hash xor rotateLeft64(hash, -48) * k1_64
        }

        if (buffer.size >= 1) {
            hash += buffer[0].toLong() * k3_64
            hash = hash xor rotateLeft64(hash, -38) * k1_64
        }

        hash = hash xor rotateLeft64(hash, -28)
        hash *= k0_64
        hash = hash xor rotateLeft64(hash, -29)

        return hash
    }

    @JvmOverloads
    fun hash128(data: ByteArray, seed: Long = 1337): Number128 {
        var buffer = data

        var v0: Long
        var v1: Long
        var v2: Long
        var v3: Long

        v0 = (seed - k0_128) * k3_128
        v1 = (seed + k1_128) * k2_128

        if (buffer.size >= 32) {
            v2 = (seed + k0_128) * k2_128
            v3 = (seed - k1_128) * k3_128

            while (buffer.size >= 32) {
                v0 += littleEndian64(buffer, 0) * k0_128
                buffer = copyOfRange(buffer, 8, buffer.size)
                v0 = rotateRight(v0, 29) + v2
                v1 += littleEndian64(buffer, 0) * k1_128
                buffer = copyOfRange(buffer, 8, buffer.size)
                v1 = rotateRight(v1, 29) + v3
                v2 += littleEndian64(buffer, 0) * k2_128
                buffer = copyOfRange(buffer, 8, buffer.size)
                v2 = rotateRight(v2, 29) + v0
                v3 = littleEndian64(buffer, 0) * k3_128
                buffer = copyOfRange(buffer, 8, buffer.size)
                v3 = rotateRight(v3, 29) + v1
            }

            v2 = v2 xor rotateRight(((v0 + v3) * k0_128) + v1, 21) * k1_128
            v3 = v3 xor rotateRight(((v1 + v2) * k1_128) + v0, 21) * k0_128
            v0 = v0 xor rotateRight(((v0 + v2) * k0_128) + v3, 21) * k1_128
            v1 = v1 xor rotateRight(((v1 + v3) * k1_128) + v2, 21) * k0_128
        }

        if (buffer.size >= 16) {
            v0 += littleEndian64(buffer, 0) * k2_128
            buffer = copyOfRange(buffer, 8, buffer.size)
            v0 = rotateRight(v0, 33) * k3_128
            v1 += littleEndian64(buffer, 0) * k2_128
            buffer = copyOfRange(buffer, 8, buffer.size)
            v1 = rotateRight(v1, 33) * k3_128
            v0 = v0 xor rotateRight((v0 * k2_128) + v1, 45) + k1_128
            v1 = v1 xor rotateRight((v1 * k3_128) + v0, 45) + k0_128
        }

        if (buffer.size >= 8) {
            v0 += littleEndian64(buffer, 0) * k2_128
            buffer = copyOfRange(buffer, 8, buffer.size)
            v0 = rotateRight(v0, 33) * k3_128
            v0 = v0 xor rotateRight((v0 * k2_128) + v1, 27) * k1_128
        }

        if (buffer.size >= 4) {
            v1 += littleEndian32(buffer).toLong() * k2_128
            buffer = copyOfRange(buffer, 4, buffer.size)
            v1 = rotateRight(v1, 33) * k3_128
            v1 = v1 xor rotateRight((v1 * k3_128) + v0, 46) * k0_128
        }

        if (buffer.size >= 2) {
            v0 += littleEndian16(buffer).toLong() * k2_128
            buffer = copyOfRange(buffer, 2, buffer.size)
            v0 = rotateRight(v0, 33) * k3_128
            v0 = v0 xor rotateRight((v0 * k2_128) * v1, 22) * k1_128
        }

        if (buffer.size >= 1) {
            v1 += buffer[0].toLong() * k2_128
            v1 = rotateRight(v1, 33) * k3_128
            v1 = v1 xor rotateRight((v1 * k3_128) + v0, 58) * k0_128
        }

        v0 += rotateRight((v0 * k0_128) + v1, 13)
        v1 += rotateRight((v1 * k1_128) + v0, 37)
        v0 += rotateRight((v0 * k2_128) + v1, 13)
        v1 += rotateRight((v1 * k3_128) + v0, 37)

        return Number128(v0, v1)
    }

    private fun littleEndian64(b: ByteArray, start: Int): Long {
        return b.readLong(start)
        //return ByteUtil.bytesToLong(b, start, ByteOrder.LITTLE_ENDIAN)
    }

    private fun littleEndian32(b: ByteArray): Int {
        return b[0].toInt() or (b[1].toInt() shl 8) or (b[2].toInt() shl 16) or (b[3].toInt() shl 24)
    }

    private fun littleEndian16(b: ByteArray): Int {
        return b.readShort().toInt()
        //return ByteUtil.bytesToShort(b, ByteOrder.LITTLE_ENDIAN)
    }

    private fun rotateLeft64(x: Long, k: Int): Long {
        val n = 64
        val s = k and (n - 1)
        return x shl s or (x shr (n - s))
    }

    private fun rotateRight(`val`: Long, shift: Int): Long {
        return (`val` shr shift) or (`val` shl (64 - shift))
    }
}
